package cc.cc4414.cc03.core.security;

import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import cc.cc4414.cc03.core.constant.RedisConsts;
import cc.cc4414.cc03.sys.entity.User;

/**
 * 自定义认证管理
 * 
 * @author cc 2019年1月5日
 */
@Service
public class AuthenticationManagerImpl implements AuthenticationManager {

	private static final String USERNAME = "username";
	private static final String CAPTCHA = "captcha";
	private static final String VISITOR_ID = "visitorId";

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Autowired
	private UserDetailsService userDetailsService;

	@Autowired
	private AuthenticationProvider daoAuthenticationProvider;

	@Bean
	public AuthenticationProvider daoAuthenticationProvider() {
		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setUserDetailsService(userDetailsService);
		// setPostAuthenticationChecks原本用于自定义最后的校验，这里用于处理校验完成后的逻辑
		daoAuthenticationProvider.setPostAuthenticationChecks(toCheck -> {
			// 清除redis中该用户尝试登录次数的记录
			stringRedisTemplate.delete(RedisConsts.LOGIN_NUMBER + ((User) toCheck).getUsername());
			stringRedisTemplate.delete(RedisConsts.LOGIN_NUMBER + ((User) toCheck).getMobile());
		});
		return daoAuthenticationProvider;
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 自定义认证逻辑
		// security框架中authentication.getDetails()实际为Map类型，忽略警告
		@SuppressWarnings("unchecked")
		Map<String, String> parameters = (Map<String, String>) authentication.getDetails();
		// 获取传入参数中的用户名
		String username = parameters.get(USERNAME);
		// 将redis中该用户名尝试登录次数加1
		stringRedisTemplate.opsForValue().increment(RedisConsts.LOGIN_NUMBER + username, 1L);
		// 获取redis中该用户名尝试登录次数
		String number = stringRedisTemplate.opsForValue().get(RedisConsts.LOGIN_NUMBER + username);
		// 设置1天过期
		stringRedisTemplate.expire(RedisConsts.LOGIN_NUMBER + username, 1L, TimeUnit.DAYS);
		// 如果1天之内尝试登录次数大于3，则进行验证码校验
		if (Integer.parseInt(number) > 3) {
			// 获取传入参数中的验证码
			String captcha = parameters.get(CAPTCHA);
			// 获取传入参数中的访客id
			String visitorId = parameters.get(VISITOR_ID);
			// 获取redis中的验证码
			String redisCaptcha = stringRedisTemplate.opsForValue().get(RedisConsts.CAPTCHA + visitorId);
			// 一个验证码只能使用一次
			stringRedisTemplate.delete(RedisConsts.CAPTCHA + visitorId);
			// 如果验证码错误，抛出BadCredentialsException异常
			if (captcha == null || !captcha.equalsIgnoreCase(redisCaptcha)) {
				throw new BadCredentialsException("验证码错误！");
			}
		}
		return daoAuthenticationProvider.authenticate(authentication);
	}

}
